#include"fiber.h"
#include<atomic>
#include"config.h"
#include"macro.h"
#include"schedule.h"

namespace sylar{

/// 使用原子量来
static std::atomic<uint64_t> s_fiber_id{0};
static std::atomic<uint64_t> s_fiber_count{0};

static thread_local Fiber* t_fiber = nullptr;    //当前协程
static thread_local Fiber::ptr t_threadFiber = nullptr;  // 旧的协程

/// 定义 协程栈大小为1M
static ConfigVar<uint32_t>::ptr g_fiber_stack_size = 
                            Config::Lookup<uint32_t>("fiber.stack_size", 1024*1024, "fiber stack size");

static sylar::Logger::ptr g_logger = SYLAR_LOG_NAME("system");

//通用的内存分配器
class MallocStackAllocator{
public:
    static void* Alloc(size_t size){
        return malloc(size);
    }
    static void Dealloc(void* vp, size_t size){
        free(vp);
    }
private:
};

using StackAllocator = MallocStackAllocator;

Fiber::Fiber(){
    /// 每个线程第一个协程的构造，主协程
    m_state = EXEC;
    SetThis(this); // 设置到线程变量 t_fiber;

    if(getcontext(&m_ctx)){
        SYLAR_ASSERT2(false, "getcontext");
    }

    ++s_fiber_count;
    SYLAR_LOG_INFO(g_logger)<< "Fiber::Fiber ctor()";
}

Fiber::Fiber(std::function<void()>cb, size_t stacksize, bool use_caller)
    :m_id(++s_fiber_id), m_cb(cb){
        ++s_fiber_count;
        m_stacksize = stacksize? stacksize : g_fiber_stack_size->getValue();

        m_stack = StackAllocator::Alloc(m_stacksize);
        if(getcontext(&m_ctx)){
            SYLAR_ASSERT2(false, "getcontext");
        }

        m_ctx.uc_link = nullptr;    // 指向当前的上下文结束时要恢复到的上下文
        // 该上下文中使用的栈
        m_ctx.uc_stack.ss_sp = m_stack;
        m_ctx.uc_stack.ss_size = m_stacksize;

        if (!use_caller) {
            makecontext(&m_ctx, &Fiber::MainFunc, 0);   // 用于将一 个新函数和堆栈，绑定到指定context中
        }
        else {  // 在MainFiber上调度
            makecontext(&m_ctx, &Fiber::CallerMainFunc, 0);
        }
        // SYLAR_LOG_INFO(g_logger)<< "Fiber::Fiber id="<<m_id;
}

Fiber::~Fiber(){
    --s_fiber_count;
    /// 主fiber不会有栈，子协程需要回收栈，所以先进行判断
    if(m_stack){

        SYLAR_ASSERT(m_state==TERM || m_state==INIT|| m_state == EXCEP);
        StackAllocator::Dealloc(m_stack, m_stacksize);
    }else{
        /// 主协程没有cb并且一直处于exec状态
        SYLAR_ASSERT(!m_cb);
        SYLAR_ASSERT(m_state==EXEC);

        Fiber*cur = t_fiber;
        if(cur==this){
            SetThis(nullptr);
        }
    }
    SYLAR_LOG_INFO(g_logger)<< "Fiber::~Fiber id="<<m_id;
}

/// 协程出问题一个把状态和函数重置, 也可以节省内存的分配，可以成为一个新的协程栈
void Fiber::reset(std::function<void()> cb){    
    /// 断言是子协程
    SYLAR_ASSERT(m_stack);
    /// 状态为INIT或TERM
    SYLAR_ASSERT(m_state==INIT || m_state==TERM || m_state==EXCEP);
    /// 满足以上条件才可以reset

    m_cb = cb;
    if(getcontext(&m_ctx)){
        SYLAR_ASSERT2(false, "getcontext");
    }
    m_ctx.uc_link = nullptr;    // 指向当前的上下文结束时要恢复到的上下文
    // 该上下文中使用的栈
    m_ctx.uc_stack.ss_sp = m_stack;
    m_ctx.uc_stack.ss_size = m_stacksize;


    makecontext(&m_ctx, &Fiber::MainFunc, 0);   // 用于将一个新函数和堆栈，绑定到指定context中
    m_state = INIT;
}

/// 切换到当前协程，开始执行
void Fiber::swapIn(){
    SetThis(this);
    SYLAR_ASSERT(m_state != EXEC);
    m_state = EXEC;
    // 函数保存当前的上下文到oucp所指向的数据结构，并且设置到ucp所指向的上下文
    // 执行绑定的函数 MainFunc
//	if(swapcontext(&t_threadFiber->m_ctx,&m_ctx)){//与当前线程的交换操作
    /// 变成与主协程交换
    if (swapcontext(&Scheduler::GetMainFiber()->m_ctx, &m_ctx)) {
        SYLAR_ASSERT2(false, "swapcontext");
    }
    // SYLAR_LOG_INFO(g_logger) << t_threadFiber->getId();
}

/// 让出执行权，把当前协程yield到后台
void Fiber::swapOut(){
    SetThis(Scheduler::GetMainFiber());
    //SetThis(t_threadFiber.get());
    if (swapcontext(&m_ctx, &Scheduler::GetMainFiber()->m_ctx)) {
        SYLAR_ASSERT2(false, "swapcontext");
    }
}

/// 设置当前线程的运行协程
void Fiber::SetThis(Fiber* fiber){
    t_fiber = fiber;
}
/// 获取当前线程正在执行的协程
Fiber::ptr Fiber::GetThis(){
    if(t_fiber){
        return t_fiber->shared_from_this();
    }
    /// 如果不存在说明当前没有主协程，否则t_fiber一般情况下应该是主协程
    Fiber:ptr main_fiber(new Fiber);
    SYLAR_ASSERT(t_fiber==main_fiber.get());
    t_threadFiber = main_fiber;
//    SYLAR_ASSERT(t_fiber==t_threadFiber.get());
    return t_fiber->shared_from_this();
}

/// 协程切换到后台并且设置为Ready状态
void Fiber::YieldToReady(){
    Fiber::ptr cur = GetThis();
    SYLAR_ASSERT(cur->m_state == EXEC);
    cur->m_state = READY;
    cur->swapOut();
}
/// 协程切换到后台，并且设置为Hold状态
void Fiber::YieldToHold(){
    Fiber::ptr cur = GetThis();
    SYLAR_ASSERT(cur->m_state == EXEC);
    cur->m_state = HOLD;
    cur->swapOut();
}

/// 追踪，统计总协程数
uint64_t Fiber::TotalFibers(){
    return s_fiber_count;
}

void Fiber::MainFunc(){
    Fiber::ptr cur = GetThis();
    SYLAR_ASSERT(cur);
    try{
        cur->m_cb();
        cur->m_cb = nullptr;
        cur->m_state = TERM;
    }catch(std::exception& ex){
        cur->m_state = EXCEP;
        SYLAR_LOG_ERROR(g_logger) << "Fiber Except: " << ex.what()
            << " fiber_id = " << cur->getId()
            << std::endl
            << sylar::BacktraceToString();
    } catch (...) {
        cur->m_state = EXCEP;
        SYLAR_LOG_ERROR(g_logger) << "Fiber Except"
                                    << " fiber_id=" << cur->getId()
                                    << std::endl
                                    << sylar::BacktraceToString();
    }

    // 去掉会存在内存泄露问题
    auto raw_ptr = cur.get();   // 子协程裸指针
    cur.reset();
    raw_ptr->swapOut(); // 使用裸指针切换到主协程执行

    SYLAR_ASSERT2(false, "never reach fiber_id = " + std::to_string(raw_ptr->getId()));
}

void Fiber::CallerMainFunc()
{
    Fiber::ptr cur = GetThis();
    SYLAR_ASSERT(cur);
    try {
        cur->m_cb();
        cur->m_cb = nullptr;
        cur->m_state = TERM;
    } catch (std::exception& ex) {
        cur->m_state = EXCEP;
        SYLAR_LOG_ERROR(g_logger) << "Fiber Except: " << ex.what()
                                    << " fiber_id=" << cur->getId()
                                    << std::endl
                                    << sylar::BacktraceToString();
    } catch (...) {
        cur->m_state = EXCEP;
        SYLAR_LOG_ERROR(g_logger) << "Fiber Except"
                                    << " fiber_id=" << cur->getId()
                                    << std::endl
                                    << sylar::BacktraceToString();
    }

    auto raw_ptr = cur.get();
    cur.reset();
    raw_ptr->back();
    SYLAR_ASSERT2(false, "never reach fiber_id=" + std::to_string(raw_ptr->getId()));
}

uint64_t Fiber::GetFiberId() {

    if(t_fiber) {
        return t_fiber->getId();
    }
    return 0;
}

/**
 * @brief 将当前线程切换到执行状态
 * @pre 执行的为当前线程的主协程
 * 相当于之前的swapIn
 */
void Fiber::call(){
    SetThis(this);
    m_state = EXEC;
    //	SYLAR_ASSERT(GetThis() == t_threadFiber);
    // SYLAR_LOG_ERROR(g_logger)<<getId();
    if(swapcontext(&t_threadFiber->m_ctx,&m_ctx)){
        SYLAR_ASSERT2(false,"swapcontext");		
    }
}
	

/**
 * @brief 相当于之前的swapOut
 * 
 */
void Fiber::back(){
    SetThis(t_threadFiber.get());
    if(swapcontext(&m_ctx,&t_threadFiber->m_ctx)){
        SYLAR_ASSERT2(false,"swapcontext");	
    }	
}

}